﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Caixa.DAO;
using Caixa.Model;
using Caixa.Model.Interface;
using System.Transactions;
using Caixa.Model.Enum;

namespace Caixa.BLL
{
	public static class ContaBLL
	{
		#region " Methods "

		#region " Get Methods "

		public static Conta GetByID(int id)
		{
			return ContaDAO.GetByID(id);
		}

		public static Conta GetByNumero(int numero)
		{
			return ContaDAO.GetByNumero(numero);
		}

		public static bool ValidaAcesso(int conta, string senha)
		{
			bool success = false;

			//Resgata a conta pelo número
			Conta c = ContaDAO.GetByNumero(conta);
			if (c == null)
				throw new Exception("Número de conta inválido.");

			//Valida se a senha é válida
			success = (c != null && c.Senha.Equals(senha));
			if (!success)
				throw new Exception("Senha inválida.");

			return success;
		}

		public static List<IDestinatarioTransacao> GetFavorecidos(int contaID, TipoPessoaEnum tipoPessoa)
		{
			return ContaDAO.GetFavorecidos(contaID, tipoPessoa);
		}

		public static bool ExisteFavorecido(int contaID, int destinatarioTransacaoID)
		{
			return ContaDAO.ExisteFavorecido(contaID, destinatarioTransacaoID);
		}

		#endregion

		#region " Management Methods "

		#region " Saque / Depósito "

		public static bool Saque(Conta c, double valor)
		{
			bool success = false;

			//Verifica se conta está ativa
			if (!c.Ativo)
				throw new Exception("Conta inativa! O saque não pode ser feito pois essa conta está inativa.");

			//Verifica se o valor para saque é maior que zero
			if (valor <= 0)
				throw new Exception("Valor inválido! Valor para saque deve ser maior que 0(zero).");

			//Verifica se a conta possui saldo para este saque
			if (c.Saldo < valor)
				throw new Exception("Saldo insuficiente! O saque não pode ser feito pois o saldo da conta é inferior ao valor do saque.");

			//Insere a movimentação referente ao saque, a qual já irá atualizar o saldo da conta
			success = (InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.Saque, Movimentacao.TipoMovimentacao.Debito, "Saque", valor) > 0);

			//Atualiza as informações da conta (mais especificamente o saldo)
			c = ContaBLL.GetByID(c.ID);

			//Se o saque zerar o saldo da conta, a conta se torna inativa
			if (success && c.Saldo.Equals(0))
			{
				c.Ativo = false;
				success = ContaBLL.Update(c);
			}

			return success;
		}

		public static bool Deposito(Conta c, double valor)
		{
			bool success = false;

			//Verifica se o valor para depósito é maior que zero
			if (valor <= 0)
				throw new Exception("Valor inválido! Valor para depósito deve ser maior que 0(zero).");

			//Insere a movimentação referente ao depósito, a qual já irá atualizar o saldo da conta
			success = (InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.Deposito, Movimentacao.TipoMovimentacao.Credito, "Depósito", valor) > 0);

			//Atualiza as informações da conta (mais especificamente o saldo)
			c = ContaBLL.GetByID(c.ID);

			//Se a conta estiver inativa e o depósito deixar o 
			//saldo da conta maior que zero, a conta se torna ativa
			if (success && (!c.Ativo) && (c.Saldo > 0))
			{
				c.Ativo = true;
				success = ContaBLL.Update(c);
			}

			return success;
		}

		#endregion

		#region " DOC "

		public static Transacao EfetuaDOC(Conta c, IDestinatarioTransacao destinatario, DateTime data, double valor)
		{
			Transacao transacao = null;

			//Verifica se o valor para DOC é maior que zero
			if (valor <= 0)
				throw new Exception("Valor inválido! Valor para DOC deve ser maior que 0(zero).");

			//Verifica se o valor para DOC é maior que 1000
			if (valor > 1000)
				throw new Exception("Valor inválido! Valor máximo para DOC deve ser R$ 1.000,00.\nPara valores superiores, utilize TED.");

			//Verifica se a conta possui saldo para este DOC
			if (c.Saldo < valor)
				throw new Exception("Saldo insuficiente! O DOC não pode ser feito pois o saldo da conta é inferior ao valor do DOC.");

			using (TransactionScope ts = new TransactionScope())
			{
				// Insere a transação referente a este DOC
				int transacaoID;
				transacaoID = InserirTransacao(c.ID, destinatario.ID, TransacaoTipo.TransacaoTipoEnum.DOC, data, valor);

				if (transacaoID > 0)
				{
					//Insere a movimentação referente a transação deste DOC			
					int numeroBanco = BancoBLL.GetByID(destinatario.IDBanco).Numero;
					string descricaoMovimentacao = string.Format("DOC Nº {0} p/ Bco: {1}", transacaoID, numeroBanco, destinatario.NumeroAgencia, destinatario.NumeroConta);
					int movimentacaoID = InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.DOC, Movimentacao.TipoMovimentacao.Debito, descricaoMovimentacao, valor);
					if (movimentacaoID > 0)
					{
						// Relaciona a movimentação a transação deste DOC
						if (TransacaoBLL.RelacionaMovimentacao(transacaoID, movimentacaoID))
						{
							// Insere movimentação descontando taxa do DOC
							descricaoMovimentacao = string.Format("Taxa DOC Nº {0} p/ Bco: {1}", transacaoID, numeroBanco, destinatario.NumeroAgencia, destinatario.NumeroConta);
							double valorTaxa = (valor * 0.0075);
							movimentacaoID = InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.Taxa, Movimentacao.TipoMovimentacao.Debito, descricaoMovimentacao, valorTaxa);
							if (TransacaoBLL.RelacionaMovimentacao(transacaoID, movimentacaoID))
								transacao = TransacaoBLL.GetByID(transacaoID);
						}
					}
				}

				ts.Complete();
			}

			return transacao;
		}

		public static Movimentacao RecebeDOC(int numeroDOC, int numeroBancoRemetente, int numeroAgenciaRemetente, int numeroContaRemetente, int numeroAgenciaDestinatario, int numeroContaDestinatario, double valor)
		{
			Movimentacao movimentacao = null;

			//Verifica se o valor para DOC é maior que zero
			if (valor <= 0)
				throw new Exception("Valor inválido! Valor para DOC deve ser maior que 0(zero).");

			//Verifica se o valor para DOC é maior que 1000
			if (valor > 1000)
				throw new Exception("Valor inválido! Valor máximo para DOC deve ser R$ 1.000,00.\nPara valores superiores, utilize TED.");

			Conta cta = ContaBLL.GetByNumero(numeroContaDestinatario);
			if (cta != null)
			{
				Agencia ag = AgenciaBLL.GetByID(cta.IDAgencia);
				if ((ag != null) && (ag.Numero.Equals(numeroAgenciaDestinatario)))
				{
					string descricaoMovimentacao = string.Format("DOC Nº {0} de Bco: {1}", numeroDOC, numeroBancoRemetente, numeroAgenciaRemetente, numeroContaRemetente);
					int movimentacaoID = InserirMovimentacao(cta.ID, MovimentacaoTipo.MovimentacaoTipoEnum.DOC, Movimentacao.TipoMovimentacao.Credito, descricaoMovimentacao, valor);
					movimentacao = MovimentacaoBLL.GetByID(movimentacaoID);
				}
			}

			return movimentacao;
		}

		public static bool DevolveDOC(int numeroDOC)
		{
			bool success = false;

			Transacao transacaoDOC = TransacaoBLL.GetByID(numeroDOC);
			if (transacaoDOC == null)
				throw new Exception(string.Format("DOC inválido! DOC Nº {0} não existe, impossível realizar estorno.", numeroDOC));

			List<Movimentacao> movimentacaoList = TransacaoBLL.GetMovimentacao(transacaoDOC.ID);
			if (movimentacaoList.Count > 0)
			{
				foreach (Movimentacao m in movimentacaoList)
				{
					// Insere movimentação estornando valor do DOC
					string descricaoMovimentacao = string.Format("Estorno ref. DOC Nº {0}", transacaoDOC.ID);
					switch (m.MovimentacaoTipo)
					{
						case MovimentacaoTipo.MovimentacaoTipoEnum.DOC:
							descricaoMovimentacao = string.Format("Estorno DOC Nº {0}", transacaoDOC.ID);
							break;
						case MovimentacaoTipo.MovimentacaoTipoEnum.Taxa:
							descricaoMovimentacao = string.Format("Estorno Taxa DOC Nº {0}", transacaoDOC.ID);
							break;
						default:
							break;
					}

					InserirMovimentacao(m.IDConta, MovimentacaoTipo.MovimentacaoTipoEnum.Estorno, Movimentacao.TipoMovimentacao.Credito, descricaoMovimentacao, m.Valor);
				}
				success = true;
			}

			return success;
		}

		#endregion

		#region " TED "

		public static Transacao EfetuaTED(Conta c, IDestinatarioTransacao destinatario, DateTime data, double valor)
		{
			Transacao transacao = null;

			//Verifica se o valor para TED é menor ou igual a 1000
			if (valor <= 1000)
				throw new Exception("Valor inválido! Valor para TED deve ser maior que R$ 1.000,00.\nPara valores inferiores, utilize DOC.");

			//Verifica se a conta possui saldo para este TED
			if (c.Saldo < valor)
				throw new Exception("Saldo insuficiente! O TED não pode ser feito pois o saldo da conta é inferior ao valor do TED.");

			using (TransactionScope ts = new TransactionScope())
			{
				// Insere a transação referente a este TED
				int transacaoID;
				transacaoID = InserirTransacao(c.ID, destinatario.ID, TransacaoTipo.TransacaoTipoEnum.TED, data, valor);

				if (transacaoID > 0)
				{
					//Insere a movimentação referente a transação deste TED			
					int numeroBanco = BancoBLL.GetByID(destinatario.IDBanco).Numero;
					string descricaoMovimentacao = string.Format("TED Nº {0} p/ Bco: {1}", transacaoID, numeroBanco, destinatario.NumeroAgencia, destinatario.NumeroConta);
					int movimentacaoID = InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.TED, Movimentacao.TipoMovimentacao.Debito, descricaoMovimentacao, valor);
					if (movimentacaoID > 0)
					{
						// Relaciona a movimentação a transação deste TED
						if (TransacaoBLL.RelacionaMovimentacao(transacaoID, movimentacaoID))
						{
							// Insere movimentação descontando taxa do TED
							descricaoMovimentacao = string.Format("Taxa TED Nº {0} p/ Bco: {1}", transacaoID, numeroBanco, destinatario.NumeroAgencia, destinatario.NumeroConta);
							double valorTaxa = (valor * 0.0075);
							movimentacaoID = InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.Taxa, Movimentacao.TipoMovimentacao.Debito, descricaoMovimentacao, valorTaxa);
							if (TransacaoBLL.RelacionaMovimentacao(transacaoID, movimentacaoID))
								transacao = TransacaoBLL.GetByID(transacaoID);
						}
					}
				}

				ts.Complete();
			}

			return transacao;
		}

		public static Movimentacao RecebeTED(int numeroTED, int numeroBancoRemetente, int numeroAgenciaRemetente, int numeroContaRemetente, int numeroAgenciaDestinatario, int numeroContaDestinatario, double valor)
		{
			Movimentacao movimentacao = null;

			//Verifica se o valor para TED é menor ou igual a 1000
			if (valor <= 1000)
				throw new Exception("Valor inválido! Valor para TED deve ser maior que R$ 1.000,00.\nPara valores inferiores, utilize DOC.");

			Conta cta = ContaBLL.GetByNumero(numeroContaDestinatario);
			if (cta != null)
			{
				Agencia ag = AgenciaBLL.GetByID(cta.IDAgencia);
				if ((ag != null) && (ag.Numero.Equals(numeroAgenciaDestinatario)))
				{
					string descricaoMovimentacao = string.Format("TED Nº {0} de Bco: {1}", numeroTED, numeroBancoRemetente, numeroAgenciaRemetente, numeroContaRemetente);
					int movimentacaoID = InserirMovimentacao(cta.ID, MovimentacaoTipo.MovimentacaoTipoEnum.TED, Movimentacao.TipoMovimentacao.Credito, descricaoMovimentacao, valor);
					movimentacao = MovimentacaoBLL.GetByID(movimentacaoID);
				}
			}

			return movimentacao;
		}

		public static bool DevolveTED(int numeroTED)
		{
			bool success = false;

			Transacao transacaoTED = TransacaoBLL.GetByID(numeroTED);
			if (transacaoTED == null)
				throw new Exception(string.Format("TED inválido! TED Nº {0} não existe, impossível realizar estorno.", numeroTED));

			List<Movimentacao> movimentacaoList = TransacaoBLL.GetMovimentacao(transacaoTED.ID);
			if (movimentacaoList.Count > 0)
			{
				foreach (Movimentacao m in movimentacaoList)
				{
					// Insere movimentação estornando valor do TED
					string descricaoMovimentacao = string.Format("Estorno ref. TED Nº {0}", transacaoTED.ID);
					switch (m.MovimentacaoTipo)
					{
						case MovimentacaoTipo.MovimentacaoTipoEnum.TED:
							descricaoMovimentacao = string.Format("Estorno TED Nº {0}", transacaoTED.ID);
							break;
						case MovimentacaoTipo.MovimentacaoTipoEnum.Taxa:
							descricaoMovimentacao = string.Format("Estorno Taxa TED Nº {0}", transacaoTED.ID);
							break;
						default:
							break;
					}

					InserirMovimentacao(m.IDConta, MovimentacaoTipo.MovimentacaoTipoEnum.Estorno, Movimentacao.TipoMovimentacao.Credito, descricaoMovimentacao, m.Valor);
				}
				success = true;
			}

			return success;
		}

		#endregion

		#region " Movimentação "

		private static int InserirMovimentacaoInicial(Conta c)
		{
			return InserirMovimentacao(c.ID, MovimentacaoTipo.MovimentacaoTipoEnum.Deposito, Movimentacao.TipoMovimentacao.Credito, "Saldo Inicial", c.Saldo);
		}

		private static int InserirMovimentacao(int contaID, MovimentacaoTipo.MovimentacaoTipoEnum movimentacaoTipo, Movimentacao.TipoMovimentacao tipo, string descricao, double valor)
		{
			return InserirMovimentacao(contaID, movimentacaoTipo, tipo, DateTime.Now, descricao, valor);
		}

		private static int InserirMovimentacao(int contaID, MovimentacaoTipo.MovimentacaoTipoEnum movimentacaoTipo, Movimentacao.TipoMovimentacao tipo, DateTime data, string descricao, double valor)
		{
			Movimentacao m = new Movimentacao();
			m.IDConta = contaID;
			m.MovimentacaoTipo = movimentacaoTipo;
			m.Tipo = tipo;
			m.Data = data;
			m.Descricao = descricao;
			m.Valor = valor;

			return MovimentacaoBLL.Insert(m);
		}

		#endregion

		#region " Favorecido "

		public static bool RelacionaFavorecido(int contaID, int destinatarioTransacaoID)
		{
			return ContaDAO.RelacionaFavorecido(contaID, destinatarioTransacaoID);
		}

		#endregion

		#region " Transação "

		private static int InserirTransacao(int contaID, int destinatarioID, TransacaoTipo.TransacaoTipoEnum transacaoTipo, double valor)
		{
			return InserirTransacao(contaID, destinatarioID, transacaoTipo, DateTime.Now, valor);
		}

		private static int InserirTransacao(int contaID, int destinatarioID, TransacaoTipo.TransacaoTipoEnum transacaoTipo, DateTime data, double valor)
		{
			Transacao t = new Transacao();
			t.IDDestinatarioTransacao = destinatarioID;
			t.TransacaoTipo = transacaoTipo;
			t.Data = data;
			t.Valor = valor;

			return TransacaoBLL.Insert(t);
		}

		#endregion

		#endregion

		#region " DDL Methods "

		public static int Insert(Conta c)
		{
			//Verifica se a conta possui saldo maior que zero
			if (c.Saldo <= 0)
				throw new Exception("O saldo da conta deve ser maior que 0(zero)!");

			//Verifica se o cadastro do cliente da conta existe
			if (!ClientePessoaFisicaBLL.Existe(c.IDCliente) && !ClientePessoaJuridicaBLL.Existe(c.IDCliente))
				throw new Exception("O cliente desta conta é inválido!");

			//Insere a conta
			c.ID = ContaDAO.Insert(c);

			//Se a conta foi inserida com sucesso,insere uma 
			//movimentação referente ao saldo inicial da conta
			if (c.ID > 0)
			{
				InserirMovimentacaoInicial(c);
			}

			return c.ID;
		}

		public static bool Update(Conta c)
		{
			return ContaDAO.Update(c);
		}

		public static bool Delete(Conta c)
		{
			return ContaDAO.Delete(c);
		}

		#endregion

		#endregion
	}
}
